﻿// INGENIOUS FRAMEWORK - Semantic Web Development Simplified
// Copyright (C) 2012 Linked Data Tools

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


// Note: Important Reference Documents

// RDF Syntax: http://www.w3.org/TR/2004/REC-rdf-concepts-20040210/
// RDF Semantics (includes implications from missing data): http://www.w3.org/TR/rdf-mt/

// TODO: LinqToOwl classes should be static, as we should have only one instance per subject globally
// Do initialization in the static constructor, so we know that they will not be initialized until first use

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.IO;
using System.CodeDom.Compiler;
using System.Threading;

using Microsoft.CSharp;

using Ingenious.Graph;

namespace Ingenious.Owl.Linq
{
    #region Supporting Classes
    public class LinqToOwlException : Exception
    {
        #region Object Lifetime
        public LinqToOwlException(string message)
            : base(message)
        {
        }

        public LinqToOwlException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
        #endregion
    }
    #endregion
    
    public enum CodeGenerationType
    {
        CSharp,
        VB,
    }

    /// <summary>
    /// Uses the code DOM to convert a binary triple list to a set of LinqToOwl classes
    /// describing the various types it contains
    /// </summary>
    public class Generator
    {
        #region Supporting Classes
        private class SubjectURIComparer : IEqualityComparer<Triple>
        {
            public bool Equals(Triple x, Triple y)
            {
                return x.Subject == y.Subject;
            }

            public int GetHashCode(Triple obj)
            {
                return obj.Subject.GetHashCode();
            }
        }
        #endregion

        #region Constants
        private const string SystemNamespace = "System";
        private const string SystemCollectionsGenericNamespace = "System.Collections.Generic";

        private const string IndentingString = @"    ";
        #endregion

        private readonly CodeCompileUnit m_codeCompileUnit = new CodeCompileUnit();
        private readonly CodeNamespaceCollection m_defaultNamespaces = new CodeNamespaceCollection();
        private readonly CodeGenerationType m_codeGenerationType;

        private readonly Dictionary<Uri, CodeTypeDeclaration> m_uriToTypeLookup = new Dictionary<Uri, CodeTypeDeclaration>();

        private readonly ManualResetEvent m_terminateEvent = new ManualResetEvent(false);
        
        #region Object Lifetime
        public Generator()
            : this(CodeGenerationType.CSharp)
        {
        }

        public Generator( CodeGenerationType codeGenerationType )
        {
            m_defaultNamespaces.Add(new CodeNamespace(SystemNamespace));
            m_defaultNamespaces.Add(new CodeNamespace(SystemCollectionsGenericNamespace));

            m_codeGenerationType = codeGenerationType;
        }
        #endregion

        #region Helper Methods
        private static string GenerateCodeOutput(CodeCompileUnit compileUnit)
        {
            string code = null;

            // Generate the code with the C# code provider.
            CSharpCodeProvider provider = new CSharpCodeProvider();
            StringBuilder stringBuilder = new StringBuilder();

            // Create a TextWriter to a StreamWriter to the output file. 
            using (StringWriter sw = new StringWriter(stringBuilder))
            {
                IndentedTextWriter tw = new IndentedTextWriter(sw, IndentingString);

                // Generate source code using the code provider
                provider.GenerateCodeFromCompileUnit(compileUnit, tw,
                    new CodeGeneratorOptions());

                // Close the output stream
                tw.Close();
                code = sw.ToString();
            }

            return code;
        }
        #endregion

        #region Code Generation
        private bool TryCreateNamespaceFromSubjectUri(Uri subjectUri, out CodeNamespace codeNamespace)
        {
            string clrNamespaceName = null;
            codeNamespace = null;

            if (CodeDomHelper.TryGetClrNamespaceFromSubjectUri(subjectUri, out clrNamespaceName))
            {
                // Form the code namespace declaration
                codeNamespace = new CodeNamespace(clrNamespaceName);
                int namespaceIndex = m_codeCompileUnit.Namespaces.IndexOf(codeNamespace);

                if (namespaceIndex != -1)
                {
                    codeNamespace = m_codeCompileUnit.Namespaces[namespaceIndex];
                }

                return true;
            }

            return false;
        }

        private bool TryCreateTypeDeclaration(Uri subjectUri, out CodeTypeDeclaration codeTypeDeclaration)
        {
            string clrTypeName = null;
            codeTypeDeclaration = null;

            if (CodeDomHelper.TryGetClrTypeNameFromSubjectUri(subjectUri, out clrTypeName))
            {
                // Form the code type declaration
                codeTypeDeclaration = new CodeTypeDeclaration(clrTypeName);
                codeTypeDeclaration.IsClass = true;
            }

            return false;
        }

        private bool TryCreateTypeMembersFromSubjectTriples(IList<Triple> triples,
            IEnumerable<IGrouping<Uri,Triple>> subjectUriGroups,
            IGrouping<Uri, Triple> subjectUriTripleGroup, CodeTypeDeclaration subjectTypeDeclaration)
        {
            foreach (Triple triple in subjectUriTripleGroup)
            {
                CodeMemberProperty objectMemberProperty = new CodeMemberProperty();

                string clrPropertyName = null;
                CodeDomHelper.TryGetClrPropertyNameFromSubjectUri(triple.Subject, out clrPropertyName);

                objectMemberProperty.Name = clrPropertyName;

                // Note: Using the string form of CodeTypeReference, we can construct types with
                // cross-dependencies, reflecting the fact that graphs in RDF can have cyclical
                // references
                if( triple.IsLiteral )
                {
                    objectMemberProperty.Type = new CodeTypeReference(triple.Value.GetType().ToString());
                }
                else
                {
                    string typeName = null;
                    CodeDomHelper.TryGetClrTypeNameFromSubjectUri(triple.Object, out typeName);

                    objectMemberProperty.Type = new CodeTypeReference(typeName);
                }
                
                // Add the member property to the type
                subjectTypeDeclaration.Members.Add(objectMemberProperty);

                // Add the predicate attribute to the object member
                IGrouping<Uri, Triple> matchingObjectGroup =
                    subjectUriGroups.Where(sug => sug.Key == triple.Object).FirstOrDefault();

                if (matchingObjectGroup == null)
                {
                    // No matching subjects in the known graph - so this is an edge predicate.
                    // Under the 'Open World Assumption' we include a reference URI
                    objectMemberProperty.CustomAttributes.Add(
                        new CodeAttributeDeclaration(
                            new CodeTypeReference(typeof(EdgePredicateAttribute)), 
                            new CodeAttributeArgument(new CodePrimitiveExpression(triple.Predicate)),
                            new CodeAttributeArgument(new CodePrimitiveExpression(triple.Object))));
                }
                else
                {
                    objectMemberProperty.CustomAttributes.Add(
                        new CodeAttributeDeclaration(
                            new CodeTypeReference(typeof(PredicateAttribute)),
                            new CodeAttributeArgument(new CodePrimitiveExpression(triple.Predicate)),
                            new CodeAttributeArgument(new CodePrimitiveExpression(triple.Object))));
                }

                if (m_terminateEvent.WaitOne(0, false))
                {
                    // Thread has been signalled to exit
                    return false;
                }
            }

            return true;
        }
        #endregion

        #region Start/Stop
        public void Start(IList<Triple> triples)
        {
            if (triples == null)
            {
                throw new ArgumentException("triples");
            }

            // Group the triples into subject URI groups
            IEnumerable<IGrouping<Uri, Triple>> subjectGroups = triples.GroupBy<Triple, Uri>(tr => tr.Subject);
            
            // First group the triples into the same subject
            IEnumerable<IGrouping<Uri, Triple>> typedSubjectUriTripleGroups
                = triples
                .GroupBy<Triple, Uri>(t => t.Subject);

            // Add the default namespaces
            m_codeCompileUnit.Namespaces.AddRange(m_defaultNamespaces);

            foreach (IGrouping<Uri, Triple> typedSubjectUriTripleGroup in typedSubjectUriTripleGroups)
            {
                Uri subjectUri = typedSubjectUriTripleGroup.Key;
        
                // Determine the namespace
                CodeNamespace subjectNamespace = null;
                CodeTypeDeclaration subjectTypeDeclaration = null;

                // Create a CLR namespace from the subject URI, excluding the fragment
                // or do we use the XML namespace of the ontology?
                if (TryCreateNamespaceFromSubjectUri(subjectUri, out subjectNamespace))
                {
                    // Create a CLR type name from the fragment
                    if (TryCreateTypeDeclaration(subjectUri, out subjectTypeDeclaration))
                    {
                        // Now the type has been created, add to the common subject namespace
                        subjectNamespace.Types.Add(subjectTypeDeclaration);

                        // Create the type members (properties)
                        if (TryCreateTypeMembersFromSubjectTriples(triples,
                            typedSubjectUriTripleGroups,
                            typedSubjectUriTripleGroup,
                            subjectTypeDeclaration))
                        {
                            subjectNamespace.Types.Add(subjectTypeDeclaration);
                        }
                        else
                        {
                            // Thread has been signalled to exit before completion
                            break;
                        }
                    }
                }
            }
        }

        public void Stop()
        {
            if (!m_terminateEvent.WaitOne(0, false))
            {
                m_terminateEvent.Set();
            }


        }
        #endregion
    }
}
